class DvaAction {
  DvaAction(this.type, this.payload);
  String type;
  dynamic payload;

  String? get namespace {
    if(type.contains('/')) {
      return type.split('/')[0];
    }
    return null;
  }
}

class DvaState {
  void fromJson(Map<String, dynamic> json){}
  Map<String, dynamic> toJson(){
    return <String, dynamic>{};
  }
}

typedef StateChangeFunction(StoreOfState lState, StoreOfState nState);

class Store {
  Store({ this.singleState = false, required this.rootState, required this.rootReducer });

  Map<String, StoreOfState> rootState;
  bool singleState = false;
  RootReducer rootReducer;
  List<StateChangeFunction> listeners = <StateChangeFunction>[];

  Map<String, StoreOfState<dynamic>> getState() {
    return rootState;
  }

  T getStateByKey<T>(String key) {
    if(!rootState.containsKey(key)) throw ArgumentError('不存在', key);
    return rootState[key]!.getState() as T;
  }

  Future<dynamic> dispatch(DvaAction action) async {
    return await rootReducer.doReducer(rootState, action);
  }

  void subscribe(StateChangeFunction f) {
    listeners.add(f);
  }

  void remove(StateChangeFunction f) {
    listeners.remove(f);
  }

  void broadcast(StoreOfState lState, StoreOfState nState) {
    for (Function f in listeners) {
      f(lState, nState);
    }
  }
}

class StoreOfState<T> {

  String? namespace;

  StoreOfState({ this.state, this.updateAt }) {
    updateAt = updateAt ?? DateTime.now();
  }
  T? state;
  DateTime? updateAt;
  T? getState() => state;
  void setState(T newState) {
    state = newState;
    updateAt = DateTime.now();
  }
  
  StoreOfState<T> clone() {
    return StoreOfState<T>(state: state, updateAt: updateAt);
  }
}

class Reducer<T> {

  Reducer({ this.store, this.onStateChange });

  Store? store;
  StoreOfState<T> initState = StoreOfState<T>();
  StoreOfState<T> lastState = StoreOfState<T>();
  StoreOfState<T> nextState = StoreOfState<T>();

  StateChangeEventFunction? onStateChange;

  bool canUpdate(StoreOfState<T> lState, StoreOfState<T> nState) {
    // 比较两个State, 目前是以最后更改时间做判断
    return lState.updateAt?.millisecondsSinceEpoch != nState.updateAt?.millisecondsSinceEpoch;
  }

  bool broadcast() {
    if(canUpdate(lastState, nextState)) {
      store?.broadcast(lastState, nextState);
      lastState = nextState.clone();
      return true;
    }
    return false;
  }

  Future<StoreOfState<T>> doReducer(StoreOfState<T> state, DvaAction action) async {
    nextState = await runReducer(state, action);
    if(broadcast() && onStateChange!= null) {
      onStateChange!(nextState, action);
    }
    return nextState;
  }
  Future<StoreOfState<T>> runReducer(StoreOfState<T> state, DvaAction action) async => state;
}

typedef void ErrorEventFunction(dynamic error,Map<String,dynamic> context);
typedef void StateChangeEventFunction(StoreOfState prevState, DvaAction action);
typedef void ReducerEventFunction(StoreOfState state, DvaAction action);
typedef void ActionEventFunction(DvaAction action);
typedef Future<Map<String, StoreOfState>> RunReducerFunction(Map<String, StoreOfState> prevState, DvaAction action);
typedef Future<Map<String, StoreOfState>> UseRunReducerFunction(Map<String, StoreOfState> prevState, DvaAction action, RunReducerFunction nextRunReducer);

class RootReducer {
  RootReducer({
    Reducer<dynamic>? reducer,
  }) {
    if(reducer!=null) {
      reducers[singleReducerName] = reducer;
      singleReducer = true;
      initState[singleReducerName] =  reducer.initState;
    }
  }
  Map<String, Reducer<dynamic>> reducers = <String, Reducer<dynamic>>{};
  bool singleReducer = false;
  Map<String, StoreOfState> initState = <String, StoreOfState>{};
  String singleReducerName = 'rootReducer';
  ErrorEventFunction onError = defaultErrorEvent;
  ActionEventFunction onAction = defaultActionEvent;
  UseRunReducerFunction useRunReducer = defaultRunReducer;

  static void defaultErrorEvent(dynamic error,dynamic context) {
    print('onError:$error');
  }

  static void defaultActionEvent(DvaAction action) {
    print('onAction:${action.type}');
  }

  static Future<Map<String, StoreOfState>> defaultRunReducer(Map<String, StoreOfState> prevState, DvaAction action, RunReducerFunction nextRunReducer) async {
    print('onActionStart:${action.type}');
    Map<String, StoreOfState> result = await nextRunReducer(prevState, action);
    print('onActionEnd:${action.type}');
    return result;
  }
  
  void clone(RootReducer rootReducer) {
    combineReducers(rootReducer.reducers);
  }

  void combineReducers(Map<String, Reducer<dynamic>> _reducers) {
    reducers.addAll(_reducers);
    singleReducer = false;
    reducers.forEach((String key, Reducer<dynamic> value){
      initState[key] = value.initState;
    });
  }

  Future<Map<String, StoreOfState>> doReducer(Map<String, StoreOfState> prevState, DvaAction action) async {
    onAction(action);
    return await useRunReducer(prevState, action, runReducer);
  }

  Future<Map<String, StoreOfState>> runReducer(Map<String, StoreOfState> prevState, DvaAction action) async {
    try {
      if(singleReducer) {
        await reducers[singleReducerName]?.doReducer(prevState[singleReducerName]!, action);
      }
      final Iterator<MapEntry<String, Reducer<dynamic>>> iterator = reducers.entries.iterator;

      while(iterator.moveNext()){
          final String key = iterator.current.key;
          final StoreOfState<dynamic> reducerState = prevState[key]!;
          reducerState.namespace = key;
          prevState[key] = await iterator.current.value.doReducer(reducerState, action);
      }
    } catch (e) {
      onError(e, <String,dynamic>{
        'rootState': prevState,
        'action': action,
        'target': this,
      });
    }
    return prevState;
  }
}

typedef Store StoreCreator(RootReducer rootReducer);
typedef StoreCreator StoreEnhancer(StoreCreator next);

Store createStore(RootReducer rootReducer) {
  return Store(singleState: rootReducer.singleReducer, rootState: rootReducer.initState, rootReducer: rootReducer );
}

extension StoreOfStateMap on Map<String, StoreOfState>? {
  dynamic getState(String key) {
    return this?[key]?.state;
  }
}